<!DOCTYPE html>
<html>
  <head>
    <title>
      AudioParam Initial Events
    </title>
    <script src="../../resources/testharness.js"></script>
    <script src="../../resources/testharnessreport.js"></script>
    <script src="../resources/audit-util.js"></script>
    <script src="../resources/audit.js"></script>
    <script src="../resources/audioparam-testing.js"></script>
  </head>
  <body>
    <script id="layout-test-code">
      let sampleRate = 48000;
      // Number of frames in a rendering quantum.
      let quantumFrames = 128;
      // Test doesn't need to run for very long.
      let renderDuration = 0.2;
      let renderFrames = renderDuration * sampleRate;
      let automationEndTime = 0.1;

      let audit = Audit.createTaskRunner();

      // The following tests start a ramp automation without an initial event.
      // This should cause an initial event to be added implicitly to give a
      // starting point.
      audit.define('linear-ramp', (task, should) => {
        runTest('Linear ramp', should, {
          automationFunction: function(node, value, time) {
            node.gain.linearRampToValueAtTime(value, time);
          },
          referenceFunction: linearRamp,
          // Experimentally determined threshold
          threshold: {absoluteThreshold: 6.0003e-8},
          // The starting value of the gain node
          v0: 0.5,
          // The target value of the automation
          v1: 0,
        }).then(() => task.done());
      });

      audit.define('exponential-ramp', (task, should) => {
        runTest('Exponential ramp', should, {
          automationFunction: function(node, value, time) {
            node.gain.exponentialRampToValueAtTime(value, time);
          },
          referenceFunction: exponentialRamp,
          threshold: {absoluteThreshold: 2.3842e-6},
          v0: 0.5,
          v1: 2,
        }).then(() => task.done());
      });

      // Same tests as above, but we delay the call to the automation function.
      // This is to verify that the we still do the right thing after the
      // context has started.
      audit.define('delayed-linear-ramp', (task, should) => {
        runTest('Delayed linear ramp', should, {
          automationFunction: function(node, value, time) {
            node.gain.linearRampToValueAtTime(value, time);
          },
          referenceFunction: linearRamp,
          // Experimentally determined threshold
          threshold: {absoluteThreshold: 9.87968e-8},
          // The starting value of the gain node
          v0: 0.5,
          // The target value of the automation
          v1: 0,
          delay: quantumFrames / sampleRate
        }).then(() => task.done());
      });

      audit.define('delayed-exponential-ramp', (task, should) => {
        runTest('Delayed exponential ramp', should, {
          automationFunction: function(node, value, time) {
            node.gain.exponentialRampToValueAtTime(value, time);
          },
          referenceFunction: exponentialRamp,
          // Experimentally determined threshold
          threshold: {absoluteThreshold: 1.3948e-5},
          // The starting value of the gain node
          v0: 0.5,
          // The target value of the automation
          v1: 2,
          delay: quantumFrames / sampleRate
        }).then(() => task.done());
      });

      audit.run();

      // Generate the expected waveform for a linear ramp starting from the
      // value |v0|, ramping to |v1| at time |endTime|.  The time of |v0| is
      // assumed to be 0.  |nFrames| is how many frames to generate.
      function linearRamp(v0, v1, startTime, endTime, nFrames) {
        let expected =
            createLinearRampArray(startTime, endTime, v0, v1, sampleRate);
        let preFiller = new Array(Math.floor(startTime * sampleRate));
        let postFiller = new Array(nFrames - Math.ceil(endTime * sampleRate));
        preFiller.fill(v0);
        return preFiller.concat(expected.concat(postFiller.fill(v1)));
      }

      // Generate the expected waveform for an exponential ramp starting from
      // the value |v0|, ramping to |v1| at time |endTime|.  The time of |v0| is
      // assumed to be 0.  |nFrames| is how many frames to generate.
      function exponentialRamp(v0, v1, startTime, endTime, nFrames) {
        let expected =
            createExponentialRampArray(startTime, endTime, v0, v1, sampleRate);
        let preFiller = new Array(Math.floor(startTime * sampleRate));
        preFiller.fill(v0);
        let postFiller = new Array(nFrames - Math.ceil(endTime * sampleRate));
        return preFiller.concat(expected.concat(postFiller.fill(v1)));
      }

      // Run an automation test. |message| is the message to use for printing
      // the results. |options| is a property bag containing the configuration
      // of the test including the following:
      //
      // automationFunction - automation function to use,
      // referenceFunction  - function generating the expected result
      // threshold          - comparison threshold
      // v0                 - starting value
      // v1                 - end value for automation
      function runTest(message, should, options) {
        let automationFunction = options.automationFunction;
        let referenceFunction = options.referenceFunction;
        let threshold = options.threshold;
        let v0 = options.v0;
        let v1 = options.v1;
        let delay = options.delay;

        let context = new OfflineAudioContext(1, renderFrames, sampleRate);

        // A constant source of amplitude 1.
        let source = context.createBufferSource();
        source.buffer = createConstantBuffer(context, 1, 1);
        source.loop = true;

        // Automation is applied to a gain node.  Set the initial value of the
        // gain in the constructor; using the value setter does an implicit
        // setValueAtTime which sets an initial event.  This defeats the purpose
        // of the test.
        let gain = new GainNode(context, {gain: v0});

        // Delay start of automation, if requested
        if (delay) {
          context.suspend(options.delay).then(function() {
            automationFunction(gain, v1, automationEndTime);
            context.resume();
          });
        } else {
          automationFunction(gain, v1, automationEndTime);
        }

        source.connect(gain);
        gain.connect(context.destination);

        source.start();

        return context.startRendering().then(function(resultBuffer) {
          let result = resultBuffer.getChannelData(0);
          let expected = referenceFunction(
              v0, v1, delay ? delay : 0, automationEndTime, renderFrames);
          should(result, message).beCloseToArray(expected, threshold);
        });
      }
    </script>
  </body>
</html>
